In previous articles we have seen how the Code Access Security model changed in .NET Framework 4.0.
In What’s new in code access security in .NET Framework 4.0 – Part I we saw how the CAS Policy System that was used until .NET Framework 3.5 has now been replaced by the Level2 Security Transparent Model. Permissions to use the protected resources granted to an assembly have been moved from the assembly itself to the host in which the assembly runs. All assemblies in a host now have the same security restrictions, thereby conforming to the Homogeneous Domain concept.
In What’s new in code access security in .NET Framework 4.0 – Part II we saw that, despite the Level2 Security Transparent Model being apparently all-or-nothing, it is, in fact, possible to use Allow Partially Trusted Caller Attribute (APTCA) to mix together SecurityTransparent, SecurityCritical and SecuritySafeCritical attributes to define granular permissions to grant to an assembly when it need to access protected resources.
In the two previous articles, we have demonstrated how the new CAS technology works, by providing some examples of simple console applications. We said that, in these cases, there is no host to manage, because any simple application will run as an unhosted application, always as full trust code.
In this article we want to analyze how the Level2 Security Transparent Model works within a hosted environment. To do so, we will consider the most important hosted environment that is used today, the ASP.NET Application Domains.
We will start by analyzing how ASP.NET application domains have been modified so as to implement the Level2 Security Transparent Model. We then see how to use configuration files to specify the permissions to grant to assemblies loaded inside these application domains. We will do this with the aid of some examples. Finally, we will see how to use APTCA assemblies in ASP.NET to define, in a more granular way, different permissions for different blocks of code, when more flexibility is required.
ASP.NET 4.0 Application Domain
As described in the MSDN library, an application domain is, “…a construct that hosts use to isolate code running within a process…”
We know that, when a managed application is executed, the .NET runtime is able to create an application domain in which the assemblies are loaded and executed. For security reasons, an application domain is isolated from other application domains, and the assemblies loaded inside it cannot overpass its boundaries.
Prior to .NET Framework 4.0, an ASP.NET application domain’s boundaries would always be executed as full trust. The old CAS Policy System was responsible for granting permissions for each group of code (defined using the code’s evidence) contained inside the application domain and those conditions were verified at the group level. This situation led to a heterogeneous application domain, in which different code groups have different permissions to execute. Because the PermissionSets assigned to each code group was generated from different sources, it was common to see a mixture, or sometimes an overlapping, of permissions.
The .NET Framework 4.0 removes even this behavior by removing the entire CAS Policy System. The new ASP.NET application domain now uses the Level2 Security Transparent Model, and the permissions granted to assemblies inside it are now defined on its boundaries. This makes the application domain a partially trusted environment and code inside it becomes Security Transparent. Because the permissions defined for the application domain are granted in the same way to all the assemblies inside it, the application domain becomes a homogeneous application domain.
ASP.NET Trust Policies
Despite of what we have said, the default behavior for ASP.NET 4.0 application domain is still to run as full trust environment. To be able to run it as a partially trusted domain we need to set a Trust Policy for it.
ASP.NET 4.0 permits four different Trust Levels: Full, High, Medium and Low. While Full (the default value) is used to create a full trusted application domain, the other three generate a partially trusted application domain, giving a set of permissions to it that are defined on the .NET Framework configuration files related to each level.
Configuration files are contained in the Config folder of your .NET Framework 4.0 installation directory (normally C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config). These files are named web_<trust-level>trust.config, where <trust-level> is the desired trust level. The following image shows a screenshot of one of this file, the web_mediumtrust.config file.
If you take a look at it, you will see that the configuration files contain this kind of node:
- a set of <SecurityClass /> xml nodes. They allow you to assign a common name to an assembly. The common name is specified in the Name xml attribute of the node, while the full assembly name is defined in the Description xml attribute. The common name is then used on the rest of the file to refer to it easily.
- a <NamedPermissionSets /> xml node. It contains:
- a set of <PermissionSet /> xml nodes. Each of them defines a set of permissions that can be applied to an application domain.
- a set of <IPermission /> nodes assigned to a specific <PermissionSet /> node. Each of them specifies a single permission granted to the application domain.
A configuration file defines three different PermissionSets:
FullTrust: It contains no permission at all (no <IPermission> xml nodes are defined). It specifies the directive Unrestricted=”True”. With it, all the permissions not mentioned on the permission set have full right to be executed. So, if the permission list is empty, the application domain will be a full-trust application domain. All code inside it, unless otherwise specified (as for example, marking the assembly as SecurityTransparent), will run as SecurityCritical.
Nothing: Same as the previous but it doesn’t contain the Unrestricted=”True” attribute. Without it, all the permissions that remain unspecified will have no right to execute. Because the list of permissions is empty, the application cannot execute.
ASP.Net: It represents the default value. It contains a list of all the permissions granted to the application domain. When this value is used, assemblies inside it run as SecurityTransparent code.
At this stage, it could be that you’ll notice something puzzling. It would seem that the following combination:
Trust Level |
NamedPermissionSet |
Full |
n.a. |
Hight |
FullTrust |
Medium |
FullTrust |
Low |
FullTrust |
perform exactly the same things. The application domain is a full trust application domain and the code inside it is SecurityCritical (unless otherwise specified). This is exactly what happens. The explanation as to why this happens is left out by this article due to space limits. To understand it, try to perform a search on the internet about the HostSecurityPolicyResolver class and how it works.
Another thing that can sound strange is the definition of the Nothing PermissionSet. What would be the point of developing an application and then preventing it from executing?
This PermissionSet is useful for administrative purpose. For security reasons, a web server administrator can set the Nothing PermissionSet at the server level, leaving to developers the ability to modify it at the site level. As you will see soon, the trust policies are defined on the web.config file of an ASP.NET application. Administrator can set up the machine’s web.config file with the Nothing trust level, allowing the developer to set its own configuration on the web.config of its application.
Let’s now do some examples. You can find the source code in the supported document section of this article.
A Demo ASP.NET Application
As a demo application, we write a simple dll library, EnvironmentBrowser.dll, that is able to get the list of all the environment variables defined on the machine that hosts the application. We then write an ASP.NET web application that shows the result to the user. We add some useful methods to the web application that are able:
- to show the security characteristics of the application domain and
- to show the security characteristics of the most interesting (in relation to the goals of the application) methods.
Our EnvironmentBrowser.dll library is made by a single class defined as below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
public class EnvironmentBrowser { /// <summary> /// Get a list of all environment variables defines for the machine /// </summary> /// <returns></returns> public static string GetEnvironmentVariableList() { IDictionary items = Environment.GetEnvironmentVariables(EnvironmentVariableTarget.Machine); StringBuilder sb = new StringBuilder(); foreach (string x in items.Keys) sb.Append(x + "; "); return sb.ToString(); } } /// <summary> /// Get the user name from the environment variables /// </summary> /// <returns></returns> public static string GetUserName() { return Environment.GetEnvironmentVariable("USERNAME"); } |
It defines these two methods:
GetEnvironmentVariableList(): It gets all the environment variables defined at the machine level and constructs a string that contains all their names separated by a semicolon.
GetUserName(): It returns the username of the logged user.
Our ASP.NET application is made by only a single page that contains the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
/// <summary> /// OnLoad Override /// </summary> /// <param id="e""></param> protected override void OnLoad(EventArgs e) { base.OnLoad(e); WriteDomainProperties(); WriteMethodsProperties(); WriteEnvironmentData(); } /// <summary> /// Write the domain properties /// </summary> private void WriteDomainProperties() { AppDomain app = AppDomain.CurrentDomain; try { WriteToPage("Is Homogeous: " + app.IsHomogenous); WriteToPage("Is FullTrusted: " + app.IsFullyTrusted); WriteToPage("Permisson Set Count: " + app.PermissionSet.Count); } catch (Exception ex) { WriteException(ex); } } /// <summary> /// Write the security property's value of the principal methods. /// </summary> private void WriteMethodsProperties() { try { WriteToPage("Method WriteEnvironmentData(): " + GetMethodProperty (new PageDefault(), "WriteEnvironmentData")); WriteToPage("Method GetEnvironmentVariableList(): " + GetMethodProperty (new EnvironmentBrowser(),"GetEnvironmentVariableList")); WriteToPage("Method GetUserName(): " + GetMethodProperty (new EnvironmentBrowser(),"GetUserName")); } catch (Exception ex) { WriteException(ex); } } /// <summary> /// Write the list of the environment variable defined for the machine /// </summary> public void WriteEnvironmentData() { try { WriteToPage("<br /> Environment Variables: "); WriteToPage(EnvironmentBrowser.GetEnvironmentVariableList()); } catch (Exception ex) { WriteException(ex); } try { WriteToPage("<br /> Current User: " + EnvironmentBrowser.GetUserName()); } catch (Exception ex) { WriteException(ex); } } /// <summary> /// Get the Security Property of a method of an object /// </summary> /// <returns></returns> private string GetMethodProperty(object obj, string methodName) { Type t = obj.GetType(); MethodInfo m = t.GetMethod(methodName); if (m.IsSecurityTransparent) return "SecurityTransparent"; if (m.IsSecuritySafeCritical) return "SecuritySafeCritical"; if (m.IsSecurityCritical) return "SecurityCritical"; return String.Empty; } |
For brevity, we have omitted all the other code not needed by our demo. The ‘code behind’ of the page implements the following methods:
- WriteDomainProperties(): It writes the security properties of the application domain.
- WriteMethodsProperties(): It writes the security properties of the main methods of the demo application.
- WriteEnvironmentData(): It writes the environmental variables list and the username of the user logged to the system.
- GetMethodProperty(): It gets the value of the security property of a method of an object.
The Full Trust Level
We know that the default trust level for ASP.NET 4.0 is the Full trust level. With it, the application domain is fully trusted, and the code inside it is SecurityCritical.
If we launch our application we get a response like this:
The application domain is homogeneous and no permissions are enforced to it. As expected, it runs as full trusted and all the monitored methods run as SecurityCritical code. The application is able to get the list of all the environment variables and display the username of the user logged.
The High Trust Level
Now we move the trust level of the application to the High trust level. To do so, we need to modify the web.config file of the same by adding the following lines:
1 2 |
<system.web> <trust level="High" /> </system.web> |
It states that, when the application domain starts, the web_hightrust.config file must be loaded and the ASP.Net PermissionSet (the default value) must be used.
If we now launch our application, we get the following output:
With the High trust level, that application domain became partially trusted and all the methods inside it now ran as SecurityTransparent methods. We have sandboxed our application domain.
When the WriteDomainProperties() executes as SecurityTransparent code, it is no more able to get the number of permissions from the PermissionSet property, because its accessor SecurityCritical. An error message is therefore displayed.
Moreover we are still able to have the list of all the environment variables defined on the machine that runs the application. This because the High trust level does not prevent the environment variables to being read. In fact, if you take a look at the web_hightrust.config file you will find the following lines of code:
1 2 3 4 |
<IPermission class="EnvironmentPermission" version="1" Unrestricted="true" /> |
The “Unrestricted=”True” attribute that is applied to the EnvironmentPermission states that the code to which the permission applies has full access to the resources protected by the permission.
The Medium Trust Level
If now we change the trust level to Medium in order to see what happens.
1 2 3 |
<system.web> <trust level="Medium" /> </system.web> |
Our output will be:
In this case we are no longer able to get the list of all the environment variables, while the username of the logged user is still accessible.
By inspecting the web_mediumtrust.config file, the directive related to the EnvironmentPermission has changed:
1 2 3 4 |
<IPermission class="EnvironmentPermission" version="1" Read="TEMP;TMP;USERNAME;OS;COMPUTERNAME" /> |
The Medium trust level allows the user to be able to read only the variables TEMP, TMP, USERNAME, OS and COMPUTERNAME. So we are able to get the username of the logged user but not the other information.
The Low Trust Level
We now try to use the Low trust level:
1 2 3 4 |
<system.web> <trust level="Low" /> </system.web> |
The application will generate the following output:
In this case neither the username nor the environment variables became accessible.
If you try to browse the web_lowtrust.config file you will see that it does not mention EnvironmentPermission at all, making it totally unavailable.
Analysis of the Exceptions
As some of you have probably noticed, the exceptions messages seen in the previous paragraph seem strange. We have said that, with a trust level lower than Full, assemblies that execute inside the application domain are marked as SecurityTransparent, but, while the attempt to get the number of permissions granted to the application domain result in a SecurityCritical violation, as expected, the exceptions related to the Environment variables browsing involves the violation of a EnvironmentPermission check. This seems have nothing to do with the SecurityTransparent code attempts to use SecurityCritical code. And this is true. So, what makes this happens ?
We know that the System.Environment class is implemented in the mscorlib.dll assembly of the .NET Framework. If we use Red Gate’s .NET Reflector to inspect the mscorlib.dll, and in particular the methods Environment.GetEnvironmentVariables() and Environment.GetEnvironmentVariable() that are used in our EnvironmentBrowser class, we see that this two method are implemented as SecuritySafeCritical:
We know that, for what we have said in the WHAT’S NEW IN CODE ACCESS SECURITY IN .NET FRAMEWORK 4.0 – PART II article, this is possible only if the assembly is an assembly marked with APTCA.
With .NET Reflector we see that this is true:
The SecurityTransparent method WriteEnvironmentData() calls the two SecuritySafeCritical methods Environment.GetEnvironmentVariables() and Environment.GetEnvironmentVariables(), so no exception occurs.
But when the partially-trusted application domain executes some (SecurityTransparent) code inside it, every security demand generates a stack walk to see if the code has the permission to access some protected resources. If, when the stack walk reaches the application domain boundaries, a security violation is detected, an exception is thrown.
In our demo, the code triggers the demand for the permission to get the environment variables list: When it reaches the application domain boundaries, the exception is thrown if the trust level is lower that High, This explains the exceptions condition that we observe in our demo.
The Conditional APCTA
Suppose that we now want to be able to get the username of the logged-in user, even if we are in Low trust level whilst, at the same time, protecting all the other environment variables. Obviously, the simplest way to do so is to modify the web_lowtrust.confing file to allow it to read the USERNAME environment variable or we can generate our own custom configuration file.
For the purpose of this article we will use a different approach. We have seen that the partially trusted application domain, when a demand is made, starts a stack-walk that reaches the application domain boundaries, and, with Low trust level, an exception occurs. To prevent this behavior, we can perform an Assert on our code in order to stop the stack-walk. As we know, we cannot perform an assert in SecurityTransparent code, so we need to “transform” our GetUserName() method in a SecuritySafeCritical method. This can be done by modifying our EnvironmentBrowser.dll adding to it the APTCA. We do not want to stop here. We want our dll to use the APTCA only in our application but, if another application (or another host in general) tries to use it, it can use the its code as SecurityTransparent. To do so, .NET Framework 4.0 uses the so-called conditional APTCA. This is declared as follow:
1 |
[assembly: AllowPartiallyTrustedCallers(PartialTrustVisibilityLevel=PartialTrustVisibilityLevel.NotVisibleByDefault)] |
This stated that the APTCA is not visible by default but only to hosts that are setup to use it.
To keep the attribute visible to our web application we need to add the following lines on code in our web.config file:
1 2 3 4 5 |
<partialTrustVisibleAssemblies> <add assemblyName="EnvironmentBrowser" publicKey="00240000048000009400000006020000 ...." /> </partialTrustVisibleAssemblies> |
We need to add the EnvironmentBrowser.dll assembly to the partially-trusted visible assemblies list of the application domain that is associated with our application.
You can see from the previous declaration that, in order to add EnvironmentBrowser.dll to such a list, we need not only to specify the assembly name, but even the public key associated with it (for brevity, we have reported only a portion of it being the same a string made by 320 characters). The public key is the one that we used to generate the strong name of the assembly (that is a necessary condition). To get the value for the public key you can use the sn.exe command line in this way:
1 |
sn.exe -Tp <assembly_name> |
In our case we obtain:
Now we can modify our GetName() method as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
/// <summary> /// Get the user name from the environment variables /// </summary> /// <returns></returns> [SecuritySafeCritical()] public static string GetUserName() { EnvironmentPermission permission = new EnvironmentPermission(EnvironmentPermissionAccess.Read, "USERNAME"); permission.Assert(); return Environment.GetEnvironmentVariable("USERNAME"); } |
We have marked the method as SecuritySafeCritical and we have created an object of type EnvironmentPermission. In its constructor we have set the permission to read the USERNAME environment variable. Then we have inserted the call to the Assert method of the object.
The final step is to compile our EnvironmentBrowser.dll library and install it in the Global Assembly Cache (that is a necessary condition).
By running our application we obtain:
You can see that now the GetUserName() method has become SecuritySafeCritical. The Assert prevents the stack walk reaching the application domain boundaries and, in this way, we are able to get the username of the user logged into the system, as expected.
Conclusion
With this third article about Code Access Security in .NET 4.0, we have completed the review of all the main changes on the CAS system introduces in .NET Framework 4.0. Things changed a lot but, despite of the time needed to learn the new features and how they works, my personal experience has taught me that all the efforts spent will be rewarded when, at the moment to put it on work, things now are really more simple, and less “time, and mind, consuming”.
With these three articles, I hope I was able to assist you to make the transition to the new CAS 4.0 model and to help you to get all the benefits that the new model can bring in your work as a developer.
Load comments